#include "os_queue.h"
#include "loader.h"
#include "scheduler.h"
#include "ksem.h"
#include "os_io.h"
#include "hash.h"

/*
 * @todo:
 * q_data_t needs to be inserted in a hash while creation, based on q_name
 * the hash shall be referenced while finding the queue.
 */
static _q_id_list_t g_q_id_list[MAX_OS_QUEUES];
static _q_id_list_t *q_id_freelist;
static uint16_t q_run_counter = 0;
static hash_t queue_hash;

void _wait_queue(uint32_t data)
{
    queue_handle_t q_handle = (queue_handle_t)data;
    if (q_handle->head.next != NULL) {
	/* Enqueue happened while invoking the kernel */
	return;
    } else {
	lock_ksem((uint32_t)q_handle->block);
    }
}


/*
* Request from a process to lookup
* the queue-id of another process
*/
void _lookup_queue(uint32_t data)
{
   err_t hash_err;

   queue_lookup_t *lookup_data = (queue_lookup_t *)data;
   if (!lookup_data) { return; }

   hash_err = lookup_hash(&queue_hash, lookup_data->q_name, &lookup_data->queue_id);
   lookup_data->err_code = hash_err;

   return;
}
   

void init_queues_data(void)
{
    /*
     * Initialize free-list of queue-ids
     */
    _q_id_list_t *p_qid = q_id_freelist = g_q_id_list;
    uint32_t i = 0;

    //printf("Initing queue's data\n");
    while (i < MAX_OS_QUEUES) {
	p_qid->index = i++;
	p_qid->run_counter = 0;
	p_qid->next_free = (p_qid + 1);
	p_qid++;
    }
    p_qid--;
    p_qid->next_free = NULL;

    create_hash(&queue_hash, 256, HASH_TYPE_STRING);

    return;
}

void _create_queue(uint32_t data)
{
    q_data_t *q_data = NULL;
    queue_handle_t q_handle = (queue_handle_t)data;
    _q_id_list_t *q_id_ptr;
    pcb_t *cur_pcb;

    q_handle->err_code = ERR_SUCCESS;

    if (q_id_freelist->next_free == NULL) {
	printf("Too many queues created in the system\n");
	q_handle->err_code = ERR_OVERFLOW;
	return;
    }

    q_data = (q_data_t *)malloc(sizeof(q_data_t));   // kernel memory
    if (!q_data) {
	q_handle->err_code = ERR_NO_MEM;
	return;
    }

    memset((uint8_t *)q_data, 0, sizeof(q_data_t));

    q_id_ptr = q_id_freelist;
    q_id_freelist = q_id_freelist->next_free;

    q_id_ptr->q_data = q_data;
    q_id_ptr->run_counter = ++q_run_counter;

    q_data->q_handle = q_handle;
    q_handle->queue_id = GET_QUEUE_ID(q_id_ptr);
    q_data->queue_id = q_handle->queue_id;

    /* printf("Created queue: Index:0x%x, Run-counter:0x%x, Queue-id:0x%x\n",
       GET_QUEUE_INDEX(q_handle->queue_id), GET_QUEUE_RUN_COUNTER(q_handle->queue_id),
       q_handle->queue_id); */

    cur_pcb = get_current_process();

    if (q_handle->q_name) {
        insert_hash(&queue_hash, q_handle->q_name, q_handle->queue_id);
    }

    q_data->owner = GET_PID_FROM_PCB(cur_pcb);

    q_data->next_queue = cur_pcb->owned_queues;
    cur_pcb->owned_queues = q_data;

    // q_id is inside q_id_ptr
    return;
}



static inline _q_id_list_t * is_valid_queue_id(q_id_t q_id)
{
    uint16_t q_index = GET_QUEUE_INDEX(q_id);
    uint16_t run_counter = GET_QUEUE_RUN_COUNTER(q_id);
    _q_id_list_t *q_id_ptr;


    if (q_index >= MAX_OS_QUEUES) {
	printf("Invalid queue: 0x%x\n", q_index);
	return NULL;
    }

    q_id_ptr = &g_q_id_list[q_index];
    //printf("q_id:0x%x, ptr:0x%x, run_counter_in_ptr:0x%x\n", q_id, q_id_ptr, q_id_ptr->run_counter);
    if (q_id_ptr->run_counter != run_counter) {
	//printf("Run counter mismatch: 0x%x, 0x%x\n", q_id_ptr->run_counter, run_counter); 
	return NULL;
    } 

    return q_id_ptr;
}

queue_handle_t qid_to_q_handle(q_id_t q_id, uint32_t pid)
{
    _q_id_list_t *q_id_ptr;

    q_id_ptr = is_valid_queue_id(q_id);
    if (!q_id_ptr) {
	// Invalid q_id passed
	printf("Invalid q_id passed: 0x%x\n", q_id);
	return NULL;
    }

    if (q_id_ptr->q_data->owner != pid) {
	// Mismatching PID
	printf("Mismatching pid while qid_to_qhandle: 0x%x, 0x%x\n", q_id_ptr->q_data->owner, pid);
	return NULL;
    }

    return q_id_ptr->q_data->q_handle;
}

void _enq_queue(uint32_t data)
{
    enq_msg_t *enq_msg = (enq_msg_t *)data;
    _q_id_list_t  *qid_ptr = NULL;
    uint8_t *rcv_data;
    queue_msg_t *q_msg;
    queue_handle_t q_handle;
    uint32_t receiver_pid;
    pcb_t *receiver;
    uint16_t total_len = 0;
    queue_msg_type_t msg_id;

#ifndef NO_VM
    uint32_t sender_pid;
    uint16_t tmp = 0;
    uint16_t i =0;
    uint8_t *msg_phys_addr;
    uint8_t *msg_virt_addr;
    q_error_t err_code;
#endif

    if (enq_msg == NULL)
    {
	printf("NULL enq_data passed to _enq_queue");
	return;
    }

    enq_msg->err_code = ERR_SUCCESS;
    msg_id = enq_msg->msg_id;

    /* validate queue id */
    if (!(qid_ptr = is_valid_queue_id(enq_msg->q_id))) {
        printf("Invalid q_id 0x%x. Message not enqueued\n", enq_msg->q_id);
	enq_msg->err_code = ERR_INVALID;
	return;
    }

    // validate message data
    if (!enq_msg->msg_data || !enq_msg->msg_len) {
	enq_msg->err_code = ERR_INVALID;
	return;
    }

    total_len = enq_msg->msg_len;
    receiver_pid = qid_ptr->q_data->owner;
    //printf("Going to enqueue to pid: 0x%x\n", receiver_pid);

#ifndef NO_VM
    sender_pid = get_current_process();
    tmp = total_len;
    err_code = ERR_SUCCESS;

    if (enq_msg->data_type == COPY_DATA) {

	/*
	 * get_phys_addr:
	 * Input virtual addr + length
	 * Output: Phy addr corresponding to virtual addr
	 * len = input length if the whole virtual range lies
	 * in a continuous physical range
	 * len = trimmed length till physical page boundary if
	 * the virtual range spans over discontinuous physical 
	 * pages
	 */
	msg_virt_addr = enq_msg->msg_data;
	msg_phys_addr = get_phys_addr(msg_virt_addr, &tmp);
	/* load page table of receiver to access its queue via its virtual addr */
	load_page_table(receiver_pid); 
	rcv_data = malloc(total_len);  /* user memory */
	if (!rcv_data) {
	    load_page_table(sender_pid);
	    enq_msg->err_code = ERR_NO_MEM;
	    return;
	}

	i = 0;
	do {
	    if (i) {
		/* not first time */
		load_page_table(sender_pid);
		msg_phys_addr = get_phys_addr(msg_virt_addr + i, &tmp);
		load_page_table(receiver_pid);
	    }
	    memcpy(rcv_data + i, msg_phys_addr, tmp);
	    i += tmp;
	    total_len -= tmp;
	    tmp = total_len;
	}while (total_len);
	total_len = i;

    } else {
	// shared data
	rcv_data = enq_msg->msg_data;
	total_len = enq_msg->msg_len;
    }

    //load_page_table(sender_pid);

#else

    if (enq_msg->data_type == COPY_DATA) {
	/* no virtural memory support yet */
	rcv_data = malloc(total_len);
	if (!rcv_data) {
	    enq_msg->err_code = ERR_NO_MEM;
	    return;
	}

	memcpy(rcv_data, enq_msg->msg_data, total_len);

    } else {
	// Shared data
	rcv_data = enq_msg->msg_data;
	total_len = enq_msg->msg_len;
    }


#endif

    /* common handling for VM/NO_VM cases */
    q_msg = (queue_msg_t *)malloc(sizeof(queue_msg_t));  // @todo: optimize this
    if (!q_msg) {
        if (enq_msg->data_type == COPY_DATA) {
	    free(rcv_data);  
	}
#ifndef NO_VM
	load_page_table(sender_pid);
#endif
	enq_msg->err_code = ERR_NO_MEM;
	return;
    }

    q_handle = qid_ptr->q_data->q_handle;
    q_msg->msg_id = msg_id;
    q_msg->msg_data = rcv_data;
    q_msg->msg_len = total_len;
    q_msg->next = NULL;

    /* @todo: make operation atomic */
    q_handle->tail->next = q_msg;
    q_handle->tail = q_msg;

    /* If receiver is blocked, unblock it */
    receiver = get_process(receiver_pid);
    if (receiver->process_state == process_waiting && \
	    receiver->blocked_res == q_handle->block)
    {
	//printf("Receiver (0x%x) unblocked and moved to ready queue\n", (uint32_t)receiver);
	add_process_to_ready_queue(receiver);
    }

#ifndef NO_VM
    load_page_table(sender_pid);
#endif

    return;
} 


void _delete_queue(uint32_t queue_id)
{
    _q_id_list_t *q_id_ptr = is_valid_queue_id(queue_id);
    q_data_t *q_data = NULL;
    q_data_t *tmp_data = NULL;
    pcb_t *cur_pcb;
    queue_handle_t q_handle;

    if (!q_id_ptr) { return; }

    q_data = q_id_ptr->q_data;

    printf("Deleting queue: 0x%x\n", queue_id);
    q_id_ptr->run_counter = 0;
    q_id_ptr->q_data = NULL;

    q_id_ptr->next_free = q_id_freelist;
    q_id_freelist = q_id_ptr;

    cur_pcb = get_current_process();

    if (cur_pcb->owned_queues == q_data) {
	cur_pcb->owned_queues = q_data->next_queue;
    } else {
	tmp_data = cur_pcb->owned_queues;
	while (tmp_data != NULL && tmp_data->next_queue != q_data) {
	    tmp_data = tmp_data->next_queue;
	}
	if (!tmp_data) {
	    printf("\nERROR, trying to delete a queue that does not exist");
	    return;
	}
	tmp_data->next_queue = q_data->next_queue;
    }

    q_handle = q_data->q_handle;
    if (q_handle->q_name) {
        remove_hash_node(&queue_hash, q_handle->q_name);
    }
    q_handle->err_code = ERR_SUCCESS;
    free((uint8_t *)q_data);

    // @todo: free messages enqueued in the queue?
    return;
}

